package com.android.lovely.ui.chat

import android.Manifest
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.FrameLayout
import android.widget.PopupWindow
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.animation.doOnEnd
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.android.lovely.R
import com.android.lovely.const.CardUL
import com.android.lovely.const.ChatStyleChange
import com.android.lovely.const.CollectRobot
import com.android.lovely.data.CardItem
import com.android.lovely.databinding.ActivityChatBinding
import com.android.lovely.databinding.LayoutChatPopBinding
import com.android.lovely.ext.setNotifyPermission
import com.android.lovely.framework.BaseActivity
import com.android.lovely.framework.UIController.showToast
import com.android.lovely.framework.doContinuousOperation
import com.android.lovely.manager.KVStore
import com.android.lovely.room.BotCacheService
import com.android.lovely.room.chat.ChatHelper
import com.android.lovely.room.chat.ChatProfileData
import com.android.lovely.room.chat.ConversationStream
import com.android.lovely.room.chat.database.BaseMessage
import com.android.lovely.room.chat.database.ImageMessage
import com.android.lovely.room.chat.database.LoadMoreMessage
import com.android.lovely.room.chat.database.ReceivedTextMessage
import com.android.lovely.room.chat.database.SendTextMessage
import com.android.lovely.ui.activity.BigCardActivity
import com.android.lovely.ui.activity.OptimizationActivity
import com.android.lovely.ui.dialog.ChatCloseDialog
import com.android.lovely.ui.dialog.ChatGemDialog
import com.android.lovely.ui.dialog.ChatRegenerateDialog
import com.android.lovely.ui.dialog.ChatSetDialog
import com.android.lovely.ui.dialog.MessageEditDialog
import com.android.lovely.ui.dialog.PayDialog
import com.android.lovely.user.UserManager
import com.kotlin.extensions.appContext
import com.kotlin.extensions.bus.FlowBus
import com.kotlin.extensions.bus.flowOf
import com.kotlin.extensions.context.ContextExt.startPage
import com.kotlin.extensions.coroutine.CoroutineExt.doOnMain
import com.kotlin.extensions.coroutine.CoroutineExt.launchSafety
import com.kotlin.extensions.dimen.Dimension.dp
import com.kotlin.extensions.keyboard.KeyboardManager
import com.kotlin.extensions.keyboard.hideKeyboard
import com.kotlin.extensions.view.ViewExt.addStatusBarMarginTop
import com.kotlin.extensions.view.ViewExt.clickable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest

/**
 * 聊天消息活动，用于显示与机器人的对话界面
 * @author why
 * @date 2025/3/27 11:03
 */

class ChatMessageActivity : BaseActivity<ActivityChatBinding>() {

    private val viewModel: ConversationViewModel by viewModels {
        viewModelFactory {
            initializer {
                ConversationViewModel(intent.getIntExtra("id", 0))
            }
        }
    }

    private val notifyPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        mBinding.clNotification.isVisible = !isGranted
    }

    private val keyboardManager: KeyboardManager by lazy { KeyboardManager(this) }
    private val generateAdapter: ChatTipAdapter by lazy { ChatTipAdapter() }
    private val introAdapter: ChatIntroAdapter by lazy { ChatIntroAdapter() }
    private val chatAdapter: InteractionAdapter by lazy { InteractionAdapter() }
    private val concatAdapter: ConcatAdapter = ConcatAdapter()

    private var followKeyboard: Boolean = true
    private var keyboardAnimator: ValueAnimator? = null

    var showChatNotify = !NotificationManagerCompat.from(appContext).areNotificationsEnabled()

    override fun initView(savedInstanceState: Bundle?) {
        setupStatusBar()
        setupRecyclerView()
        bindAdapterListener()
        setupClickListeners()
        setupTextChangeListener()
        observeViewModelData()
        initNotifyView()
        setupKeyboardListener()
//        viewModel.initMessage()
        viewModel.fetchRecommendations { }
    }

    private fun setupStatusBar() {
        mBinding.llTop.addStatusBarMarginTop()
        mBinding.clNotification.addStatusBarMarginTop()
    }

    private fun setupRecyclerView() {
        concatAdapter.addAdapter(chatAdapter)
        concatAdapter.addAdapter(introAdapter)
        concatAdapter.addAdapter(generateAdapter)

        val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
        mBinding.rvChat.layoutManager = layoutManager
        mBinding.rvChat.adapter = concatAdapter
        generateAdapter.updateList(arrayListOf(""))

        mBinding.rvChat.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                handleScrollStateChange(newState)
            }
        })

        mBinding.rvChat.setOnTouchListener { _, _ ->
            hideKeyboard()
            false
        }
    }

    private fun handleScrollStateChange(newState: Int) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            mBinding.pay.animate().alpha(1f).translationX(0.dp.toFloat()).start()
        } else {
            if (mBinding.pay.translationX == 0f) {
                mBinding.pay.animate().alpha(0.7f).translationX(60.dp.toFloat()).start()
            }
        }
    }

    private fun setupClickListeners() {
        mBinding.ivSend.apply {
            isEnabled = false
            clickable {
                handleSendButtonClick()
            }
        }

        mBinding.ivSendImage.clickable {
            handleSendImageClick()
        }

        mBinding.collect.clickable {
            viewModel.saveAgentToFavorites()
        }

        mBinding.menu.clickable {
            openChatSettings()
        }

        mBinding.pay.clickable {
            hideKeyboard()
            mBinding.pay.postDelayed({
                ChatGemDialog().apply {
                    arguments = bundleOf(CHAT_ID to viewModel.botId)
                }.show(supportFragmentManager)
            }, keyboardManager.keyboardOpenDuration)
        }

        mBinding.ivLeft.clickable {
            onBackPressedDispatcher.onBackPressed()
        }

        setupBackPressedCallback()
    }

    private fun handleSendButtonClick() {
        val msg = mBinding.edit.text?.toString()
        if (UserManager.isB()) {
            if (msg.isNullOrBlank().not()) {
                send(msg.orEmpty())
            } else {
                if (viewModel.character.chatModel) {
                    openChatSettings()
                }
            }
        } else {
            if (msg.isNullOrBlank()) return
            send(msg)
        }
    }

    private fun handleSendImageClick() {
        if (mBinding.ivSendImage.isSelected.not()) {
            showToast(getString(R.string.no_new_photo))
            return
        }
        mBinding.ivSendImage.isEnabled = false
        viewModel.requestMediaContent {
            mBinding.ivSendImage.isEnabled = true
        }
    }

    private fun openChatSettings() {
        ChatSetDialog().apply {
            arguments = bundleOf(CHAT_ID to viewModel.botId)
        }.show(supportFragmentManager)
    }

    private fun setupBackPressedCallback() {
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (!ChatCloseDialog.showKeep) {
                    ChatCloseDialog.showKeep = true
                    ChatCloseDialog().apply {
                        arguments = bundleOf(CHAT_ID to viewModel.botId)
                    }.show(supportFragmentManager)
                    return
                }
                isEnabled = false
                onBackPressedDispatcher.onBackPressed()
            }
        })
    }

    private fun setupTextChangeListener() {
        mBinding.edit.addTextChangedListener {
            listenTextChanged(it)
        }
    }

    private fun observeViewModelData() {
        lifecycleScope.launchSafety {
            viewModel.infoChangedFlow.collectLatest {
                onProfile(it)
            }
        }

        lifecycleScope.launchSafety {
            viewModel.eventFlow.collect { event ->
                when (event) {

                    is ConversationEvent.MessageReceived -> {
                        updateMessage(event.message)
                    }

                    is ConversationEvent.MessagesReceived -> {
                        chatAdapter.add(event.messages, 0)
                        mBinding.rvChat.scrollToPosition(0)
                    }

                    is ConversationEvent.MessageDeleted -> {
                        chatAdapter.remove(event.message)
                    }

                    is ConversationEvent.HistoryLoaded -> {
                        chatAdapter.updateList(event.messages)
                        mBinding.rvChat.scrollToPosition(0)
                    }

                    is ConversationEvent.ResetMessages -> {
                        chatAdapter.updateList(emptyList())
                    }
                }
            }
        }

        lifecycleScope.launchSafety {
            FlowBus.flowOf<ChatStyleChange>().collectLatest {
                chatAdapter.notifyDataSetChanged()
            }
        }

        lifecycleScope.launchSafety {
            FlowBus.flowOf<CollectRobot>().collectLatest {
                handleCollectRobotEvent(it)
            }
        }

        lifecycleScope.launchSafety {
            FlowBus.flowOf<CardUL>().collectLatest {
                handleCardUnlockEvent(it)
            }
        }
    }

    private fun handleCollectRobotEvent(event: CollectRobot) {
        if (event.id == viewModel.botId) {
            mBinding.collect.isSelected = mBinding.collect.isSelected.not()
            BotCacheService.updateBot(viewModel.botId) {
                isCollect = if (mBinding.collect.isSelected) 1 else 0
            }
        }
    }

    private fun handleCardUnlockEvent(event: CardUL) {
        event.ids.forEach { cardId ->
            chatAdapter.getList().forEach { message ->
                if (message is ImageMessage) {
                    if (message.cardId == cardId) {
                        message.unlock = true
                        ChatHelper.updateMessage(message.id) {
                            unlock = true
                        }
                        updateMessage(message)
                    }
                }
            }
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun initNotifyView() {
        mBinding.clNotification.isVisible = showChatNotify

        mBinding.ivNotifyClose.clickable {
            dismissNotification()
        }

        mBinding.clNotification.clickable {
            requestNotificationPermission()
        }
    }

    private fun dismissNotification() {
        showChatNotify = false
        mBinding.clNotification.animate()
            .translationY(-mBinding.clNotification.height.toFloat())
            .withEndAction {
                runCatching {
                    mBinding.clNotification.isVisible = false
                }
            }.setDuration(300).start()
    }

    private fun requestNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                notifyPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
            }
        } else {
            setNotifyPermission()
        }
    }

    private fun setupKeyboardListener() {
        keyboardManager.setOnKeyboardStateListener {
            onProgress {
                if (!followKeyboard) return@onProgress
                mBinding.clBottom.updateLayoutParams<MarginLayoutParams> { bottomMargin = it }
            }

            onClose {
                mBinding.edit.clearFocus()
                mBinding.clBottom.updateLayoutParams<MarginLayoutParams> { bottomMargin = 0 }
            }
        }
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        changeControllers(hasFocus)
    }

    override fun onResume() {
        super.onResume()
        mBinding.clNotification.isVisible = !NotificationManagerCompat.from(appContext)
            .areNotificationsEnabled() && showChatNotify
    }

    override fun onDestroy() {
        super.onDestroy()
        keyboardAnimator?.cancel()
    }

    private fun changeControllers(hasFocus: Boolean) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!hasFocus && keyboardManager.isKeyboardOpen && mBinding.clBottom.marginBottom > 0) {
                followKeyboard = false
                mBinding.edit.clearFocus()
                mBinding.edit.hideKeyboard()
                keyboardAnimator = ValueAnimator.ofInt(mBinding.clBottom.marginBottom, 0).apply {
                    duration = keyboardManager.keyboardOpenDuration
                    doOnEnd { followKeyboard = true }
                    addUpdateListener {
                        val progress = it.animatedValue as Int
                        mBinding.clBottom.updateLayoutParams<MarginLayoutParams> {
                            bottomMargin = progress
                        }
                    }
                    start()
                }
            }
        }
    }

    private fun bindAdapterListener() {
        chatAdapter.setInteractionCallback(object : ContentInteractionCallback {
            override fun onMediaClick(message: ImageMessage) {
                this@ChatMessageActivity.onImageClick(message)
            }

            override fun onModifyClick(message: ReceivedTextMessage) {
                MessageEditDialog().apply {
                    arguments = bundleOf(
                        "message" to message,
                        CHAT_ID to viewModel.botId
                    )
                }.show(supportFragmentManager)
            }

            override fun onRefreshClick(message: ReceivedTextMessage) {
                if (KVStore.regenerateRemind) {
                    ChatRegenerateDialog().apply {
                        arguments = bundleOf(
                            "message" to message,
                            CHAT_ID to viewModel.botId
                        )
                    }.show(supportFragmentManager)
                } else {
                    viewModel.refreshMessageContent(message)
                }
            }

            override fun onRetryClick(message: SendTextMessage) {
                resend(message)
            }

            override fun onFetchMore() {
                super.onFetchMore()
                handleLoadMoreMessages()
            }

            override fun onExtendedClick(message: BaseMessage, view: View) {
                super.onExtendedClick(message, view)
                showMessagePop(view, message)
            }
        })
    }

    private fun handleLoadMoreMessages() {
        lifecycleScope.launchSafety {
            doContinuousOperation {
                val (hasMore, moreMessage) =
                    viewModel.fetchMoreHistory(
                        chatAdapter.items.lastOrNull { it !is LoadMoreMessage }?.id
                            ?: return@doContinuousOperation
                    )
                doOnMain {
                    chatAdapter.remove(LoadMoreMessage)
                    chatAdapter.add(moreMessage, chatAdapter.getList().size)
                    chatAdapter.completeProcessing()
                    if (hasMore) {
                        chatAdapter.add(LoadMoreMessage, chatAdapter.itemCount)
                    }
                }
            }
        }
    }

    private fun onProfile(profileData: ChatProfileData) {
        updatePhotoAndSendButton(profileData)
        updateFreeMessageCounter(profileData)
        updateScenarioInfo(profileData)
        updateRoleInfo(profileData)
        updatePayButton(profileData)
    }

    private fun updatePhotoAndSendButton(profileData: ChatProfileData) {
        mBinding.ivSendImage.isSelected = profileData.photoEnable
        if (UserManager.isB() && profileData.chatModel && mBinding.edit.text.isNullOrEmpty()) {
            mBinding.ivSend.isEnabled = true
            mBinding.ivSend.isSelected = true
        }
    }

    private fun updateFreeMessageCounter(profileData: ChatProfileData) {
        mBinding.count.isVisible = profileData.freeNum > 0
        mBinding.count.text = if (profileData.freeNum > 99) "99+ " else "${profileData.freeNum} "
    }

    private fun updateScenarioInfo(profileData: ChatProfileData) {
        if (profileData.scenario.isNotEmpty()) {
            introAdapter.updateList(listOf(profileData.scenario))
        }
    }

    private fun updateRoleInfo(profileData: ChatProfileData) {
        val role = profileData.info
        mBinding.tvName.text = role.name
        mBinding.tvStar.text = role.intimacyValue.toString()
        mBinding.collect.isSelected = role.isCollect == 1

        if (mBinding.ivBg.tag == role.background) return
        mBinding.ivBg.tag = role.background
        mBinding.ivBg.load(role.background) { crossfade(true) }
    }

    private fun updatePayButton(profileData: ChatProfileData) {
        mBinding.pay.isVisible = profileData.canPay()
    }

    private fun listenTextChanged(it: Editable?) {
        if (viewModel.character.chatModel && UserManager.isB()) {
            if (it.isNullOrEmpty()) {
                mBinding.ivSend.isSelected = true
                mBinding.ivSend.isEnabled = true
            } else {
                mBinding.ivSend.isSelected = false
                mBinding.ivSend.isEnabled = true
            }
        } else {
            mBinding.ivSend.isSelected = false
            mBinding.ivSend.isEnabled = !viewModel.isSending
        }
    }

    private fun updateMessage(message: BaseMessage) {
        val position = chatAdapter.findIndex { it.id == message.id }
        if (position == -1) {
            chatAdapter.add(message)
            mBinding.rvChat.post {
                val layoutManager = mBinding.rvChat.layoutManager as LinearLayoutManager
                if (layoutManager.findFirstVisibleItemPosition() != 0) {
                    mBinding.rvChat.smoothScrollToPosition(0)
                }
            }
        } else {
            chatAdapter.set(position, message)
        }
    }

    private fun showMessagePop(authorView: View, message: BaseMessage) {
        hideKeyboard()

        val popupViewBinding = LayoutChatPopBinding.inflate(
            layoutInflater, FrameLayout(this), false
        )
        val popupWindow = PopupWindow(popupViewBinding.root, -2, 55.dp, true)
        popupWindow.showAsDropDown(
            authorView, 5.dp, (-5).dp, Gravity.START
        )

        configurePopupOptions(popupViewBinding, message)
        setupPopupClickListeners(popupViewBinding, popupWindow, message)
    }

    private fun configurePopupOptions(binding: LayoutChatPopBinding, message: BaseMessage) {
        binding.translate.isVisible =
            message is ReceivedTextMessage && message.translate.isEmpty() &&
                    message.translating.not() && message.id >= 0
        binding.copy.isVisible = message is ReceivedTextMessage || message is SendTextMessage
        binding.report.isVisible = message is ReceivedTextMessage
    }

    private fun setupPopupClickListeners(
        binding: LayoutChatPopBinding,
        popupWindow: PopupWindow,
        message: BaseMessage
    ) {
        binding.delete.clickable {
            viewModel.removeMessageItem(message)
            popupWindow.dismiss()
        }

        binding.report.clickable {
            startPage<OptimizationActivity>("user_id" to viewModel.botId)
            popupWindow.dismiss()
        }

        binding.copy.clickable {
            copyMessageToClipboard(message.message)
            popupWindow.dismiss()
        }

        binding.translate.clickable {
            popupWindow.dismiss()
            (message as? ReceivedTextMessage)?.run {
                viewModel.translateMessage(message)
            }
        }
    }

    private fun copyMessageToClipboard(text: String) {
        val clipboard = appContext.getSystemService(ClipboardManager::class.java)
        val clip = ClipData.newPlainText(appContext.getString(R.string.app_name), text)
        clipboard.setPrimaryClip(clip)
        showToast(getString(R.string.copied))
    }

    override fun onRecharge() {
        if(supportFragmentManager.findFragmentByTag("payDialog")!= null) return
        PayDialog().show(supportFragmentManager, "payDialog")
    }

    private fun onImageClick(message: ImageMessage) {
        if (!message.unlock) {
            viewModel.processContentAccess(message)
        } else {
            openBigCardActivity(message)
        }
    }

    private fun openBigCardActivity(message: ImageMessage) {
        BigCardActivity.start(
            this,
            viewModel.botId,
            0,
            mBinding.tvName.text.toString(),
            mutableListOf(
                CardItem(
                    message.message,
                    message.cardId,
                    message.intimacyValue,
                    message.unlock,
                    message.point
                )
            )
        )
    }

    private fun send(msg: String) {
        hideKeyboard()
        lookSending(viewModel.deliverMessage(msg))
        mBinding.edit.text?.clear()
        mBinding.edit.clearFocus()
    }

    private fun resend(msg: SendTextMessage) {
        hideKeyboard()
        lookSending(viewModel.retrySendMessage(msg))
        mBinding.edit.text?.clear()
        mBinding.edit.clearFocus()
    }

    private fun lookSending(flow: Flow<ConversationStream.StreamStatus>) {
        lifecycleScope.launchSafety {
            flow.collect {
                updateUIForMessageStatus(it)
                if (it == ConversationStream.StreamStatus.Start) {
                    resetChatEdits()
                }
                if (shouldCancelFlow(it)) {
                    cancel()
                }
            }
        }
    }

    private fun updateUIForMessageStatus(status: ConversationStream.StreamStatus) {
        val isSending = status == ConversationStream.StreamStatus.Start ||
                status is ConversationStream.StreamStatus.Receiving
        viewModel.updateSendingState(isSending)

        val enable = !isSending
        mBinding.edit.isEnabled = enable
        mBinding.ivSendImage.isEnabled = enable
        mBinding.ivSend.isEnabled = if (UserManager.isB()) {
            enable
        } else {
            enable && mBinding.edit.text.isNullOrEmpty().not()
        }
    }

    private fun resetChatEdits() {
        chatAdapter.getList().filterIsInstance<ReceivedTextMessage>()
            .forEach { it.showEdit = false }
        chatAdapter.notifyDataSetChanged()
    }

    private fun shouldCancelFlow(status: ConversationStream.StreamStatus): Boolean {
        return status != ConversationStream.StreamStatus.Start &&
                status !is ConversationStream.StreamStatus.Receiving
    }

    companion object {
        val CHAT_ID = "chat_botId"
    }
}

fun Activity.getChatId(): Int {
    return intent.getIntExtra(ChatMessageActivity.CHAT_ID, 0)
}

fun Fragment.getChatId(): Int {
    return arguments?.getInt(ChatMessageActivity.CHAT_ID, 0) ?: 0
}